package geradatawarehouse.runnable.abs;

import geradatawarehouse.dao.postgres.AbstractAlunoPostgresDAO;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;

import org.apache.log4j.Logger;

public abstract class AbstractAlunoRecorder<T> implements Runnable {

	private static final Logger LOGGER = Logger
			.getLogger(AbstractAlunoRecorder.class);

	/**
	 * Limite do tamanho da fila de objetos AlunoTurma a inserir.
	 */
	private static final int DEFAULT_QUEUE_LIMIT = 1000;

	private Thread loaderThread;

	/**
	 * Tempo que o thread desta classe ficará parado até verificar novamente se
	 * há objetos na fila.
	 */
	private static final int QUEUE_WAITING_MILLIS = 100;

	private final AbstractAlunoPostgresDAO<T> dao;

	private final ArrayBlockingQueue<T> queue = new ArrayBlockingQueue<T>(
			AbstractAlunoRecorder.DEFAULT_QUEUE_LIMIT);

	/**
	 * flag para finalização da thread.
	 */
	private boolean stopNow = false;

	/**
	 * Flag para o truncamento da tabela.
	 */
	private boolean truncated = false;

	protected AbstractAlunoRecorder(final AbstractAlunoPostgresDAO<T> dao) {
		this.dao = dao;
	}

	private void clearOnlyOnce() throws SQLException {
		if (!this.truncated) {

			this.dao.clearOnlyOnce();

			this.truncated = true;
		}
	}

	private List<T> extractFromQueue() {
		final List<T> lista = new ArrayList<T>();

		synchronized (this.queue) {
			while (!this.queue.isEmpty()) {
				lista.add(this.queue.poll());
			}
		}

		return lista;
	}

	private void insert(final List<T> lista) throws SQLException {
		this.dao.startTransaction();

		this.clearOnlyOnce();

		this.dao.record(lista);
	}

	public void queue(final T a) throws InterruptedException {
		this.queue.put(a);
	}

	@Override
	public void run() {
		try {
			while (!this.stopNow || !this.queue.isEmpty()) {
				if (this.queue.isEmpty()) {
					this.waitSomeTime();
				} else {
					final List<T> lista = this.extractFromQueue();

					this.insert(lista);
				}
			}
			// Fim das inserções seguido do commit no transaction.
			this.dao.commitTransaction();
		} catch (Exception e) {
			AbstractAlunoRecorder.LOGGER
					.error("Exceção ocorrida. Finalizando thread " + this
							+ " \nExceção: " + e);
			if (this.loaderThread != null) {
				this.loaderThread.interrupt();
			}
		}
	}

	public void setLoaderThread(Thread loaderThread) {
		this.loaderThread = loaderThread;
	}

	/**
	 * Sinaliza para outros trexos do código que há a ordem para finalizar a
	 * execução.
	 */
	public void stop() {
		this.stopNow = true;
	}

	protected void waitSomeTime() throws InterruptedException {
		Thread.sleep(AbstractAlunoRecorder.QUEUE_WAITING_MILLIS);
		return;
	}

}